package de.upb.aquarium.api.shader;

import java.io.File;
import java.util.List;

import com.sun.opengl.cg.CGcontext;
import com.sun.opengl.cg.CGparameter;
import com.sun.opengl.cg.CgGL;

import de.upb.aquarium.World;
import de.upb.aquarium.api.light.Light;
import de.upb.aquarium.api.light.PointLightSource;
import de.upb.aquarium.api.utility.VectorFloat;

public class WaterPerPixelShader {

	private final static File vertexShaderProgramm		= new File("./shaders/WaterPerPixel.vp");
	private final static File fragmentShaderProgramm	= new File("./shaders/WaterPerPixel.fp");
	
	private CgShader vertexShader;
	private CgShader fragmentShader;

	/*
	 * Fragment program parameters: 
	 */
	private CGparameter inverseModelView;
	private CGparameter modelViewProjectionFrag;

	private CGparameter absorptionAmbient;
	private CGparameter absorptionDiffuse;	
	private CGparameter absorptionSpecular;
	private CGparameter shininess;
	
	private CGparameter lightPosition;
	
	private CGparameter surfaceNormal;

	private CGparameter refractionTexture;
	private CGparameter reflectionTexture;
	
	private CGparameter distanceToRefraction;
	private CGparameter distanceToReflection;

	private CGparameter bumpMap;
	private CGparameter bumpMapSize;
	private CGparameter bumpOrigin;

	/*
	 * Vertex program parameters:
	 */
	private CGparameter modelViewProjectionVert;
	
	private CGparameter viewportSize;
	private CGparameter texSize;
	
	private CGparameter dimension;
	
	
	public WaterPerPixelShader(CGcontext context) {
		CgGL.cgGLSetManageTextureParameters(context, true);
		
		vertexShader = new CgShader(vertexShaderProgramm, context, CgGL.CG_GL_VERTEX);
		fragmentShader = new CgShader(fragmentShaderProgramm, context, CgGL.CG_GL_FRAGMENT);
		
		/*
		 * Fragment program parameters:
		 */
		inverseModelView		= fragmentShader.getProgramParameter("modelViewInv");
		modelViewProjectionFrag	= fragmentShader.getProgramParameter("modelViewProj");
				
		absorptionAmbient		= fragmentShader.getProgramParameter("Ka");
		absorptionDiffuse		= fragmentShader.getProgramParameter("Kd");
		absorptionSpecular		= fragmentShader.getProgramParameter("Ks");
		shininess				= fragmentShader.getProgramParameter("shininess");
				
		lightPosition			= fragmentShader.getProgramParameter("lightPosition");
		
		surfaceNormal			= fragmentShader.getProgramParameter("waterNormal");
		
		refractionTexture		= fragmentShader.getProgramParameter("refractionTexture");
		reflectionTexture		= fragmentShader.getProgramParameter("reflectionTexture");
		
		distanceToRefraction	= fragmentShader.getProgramParameter("distanceToRefraction");
		distanceToReflection	= fragmentShader.getProgramParameter("distanceToReflection");

		bumpMap					= fragmentShader.getProgramParameter("bumpMap");
		bumpMapSize				= fragmentShader.getProgramParameter("bumpMapSize");
		bumpOrigin				= fragmentShader.getProgramParameter("bumpOrigin");
		
		/*
		 * Vertex program parameters:
		 */
		modelViewProjectionVert	= vertexShader.getProgramParameter("modelViewProj");
		
		viewportSize			= vertexShader.getProgramParameter("viewportSize");
		texSize					= vertexShader.getProgramParameter("texSize");
		
		dimension				= vertexShader.getProgramParameter("dimension");
	}
	
	public void activate()
	{	
		// Set projection matrices.
		CgGL.cgGLSetStateMatrixParameter(modelViewProjectionVert,
				CgGL.CG_GL_MODELVIEW_PROJECTION_MATRIX, CgGL.CG_GL_MATRIX_IDENTITY);
		CgGL.cgGLSetStateMatrixParameter(modelViewProjectionFrag,
				CgGL.CG_GL_MODELVIEW_PROJECTION_MATRIX, CgGL.CG_GL_MATRIX_IDENTITY);
		
		CgGL.cgGLSetStateMatrixParameter(inverseModelView,
				CgGL.CG_GL_MODELVIEW_MATRIX, CgGL.CG_GL_MATRIX_INVERSE);
		
		// Activate shaders
		vertexShader.activate();
		fragmentShader.activate();
	}
	
	public void deactivate()
	{
		fragmentShader.deactivate();
		vertexShader.deactivate();
	}
	
	public void setAmbientAbsorption(float r, float g, float b)
	{
		CgGL.cgGLSetParameter3f(absorptionAmbient, r, g, b);
	}
	
	public void setDiffuseAbsorption(float r, float g, float b)
	{
		CgGL.cgGLSetParameter3f(absorptionDiffuse, r, g, b);
	}
	
	public void setSpecularAbsorption(float r, float g, float b, float shininess)
	{
		CgGL.cgGLSetParameter3f(absorptionSpecular, r, g, b);
		CgGL.cgGLSetParameter1f(this.shininess, shininess);
	}
	
	
	public void setLights(VectorFloat cameraPosition)
	{
		List<Light> glLights = World.getInstance().getLights();
		
		int numLights = 0;
		for (Light light : glLights)
		{
			if (light.isOn() && (light instanceof PointLightSource))
			{
				numLights++;
			}
		}
		
		int lightIndex = 0;
		
		for (Light light : glLights)
		{
			if (!light.isOn() || !(light instanceof PointLightSource))
			{
				continue;
			}
			
			PointLightSource pointLight = (PointLightSource)light;
			
			CGparameter position	= CgGL.cgGetArrayParameter(lightPosition, lightIndex);
			VectorFloat lightPosition = cameraPosition.sub(pointLight.getPosition());
			CgGL.cgGLSetParameter3fv(position, lightPosition.data, 0);
			
			lightIndex++;
		}
		
		float[] zero = new float[] {0.0f, 0.0f, 0.0f};
		for (; lightIndex < 8; lightIndex++)
		{
			CGparameter position	= CgGL.cgGetArrayParameter(lightPosition, lightIndex);
			CgGL.cgGLSetParameter3fv(position, zero, 0);
		}
	}
	
	
	public void setSurfaceNormal(double x, double y, double z)
	{
		CgGL.cgGLSetParameter3d(this.surfaceNormal, x, y, z);
	}
	
	public void setDistanceToRefraction(float distance)
	{
		CgGL.cgGLSetParameter1f(distanceToRefraction, distance);
	}
	
	public void setDistanceToReflection(float distance)
	{
		CgGL.cgGLSetParameter1f(distanceToReflection, distance);
	}
	
	public void setRefractionTexture(int textureId)
	{
		CgGL.cgGLSetTextureParameter(refractionTexture, textureId);
	}
	
	public void setReflectionTexture(int textureId)
	{
		CgGL.cgGLSetTextureParameter(reflectionTexture, textureId);
	}
	
	public void setBumpMap(int textureId, float originX, float originY, float width, float height)
	{
		CgGL.cgGLSetTextureParameter(bumpMap, textureId);
		CgGL.cgGLSetParameter2f(bumpOrigin, originX, originY);
		CgGL.cgGLSetParameter2f(bumpMapSize, width, height);
	}
	
	public void setTextureParameters(
			double width, double height,
			double viewportWidth, double viewportHeight)
	{
		CgGL.cgGLSetParameter2d(viewportSize, viewportWidth, viewportHeight);
		CgGL.cgGLSetParameter2d(texSize, width, height);
	}
	
	public void setDimension(double width, double depth)
	{
		CgGL.cgGLSetParameter2d(dimension, width, depth);
	}
}
